/*
 * Created on Feb 12, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.schema.impl;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.vocabulary.RDF;

import edu.mit.simile.longwell.Cache;
import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.schema.IStructuredSchema;

/**
 * @author dfhuynh
 */
public class StructuredSchema implements IStructuredSchema {
	final static private Logger s_logger = 
		Logger.getLogger(StructuredSchema.class);
	
	static protected Map s_uriToRecord = new HashMap();
	static protected Map s_uriToProfileToInstance = new HashMap();
	static protected Cache s_allObjectsCache = new Cache(10, StructuredSchema.class, "all-objects");
	
	static class Record {
		String		m_uri;
		
		boolean		m_explicit;
		
		Set			m_classes = new HashSet();
	}
	
	final Profile	m_profile;
	final Record	m_record;
	protected Set	m_classes; // cached
	
	protected StructuredSchema(Profile profile, Record record) {
		m_profile = profile;
		m_record = record;		
	}
	
	static public void dispose(Profile profile) {
		synchronized (s_uriToProfileToInstance) {
			Iterator i = s_uriToProfileToInstance.values().iterator();
			while (i.hasNext()) {
				Map profileToInstance = (Map) i.next();
				StructuredSchema sSchema = (StructuredSchema) 
					profileToInstance.remove(profile);
				
				s_allObjectsCache.remove(sSchema);
			}
		}
	}
	
	static void load(File file) {
		if (!file.exists()) {
			return;
		}
		
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			
			ObjectInputStream ois = new ObjectInputStream(fis);
			
			int count = ois.readInt();
			
			for (int i = 0; i < count; i++) {
				Record record = getRecord((String) ois.readObject());

				record.m_explicit = ois.readBoolean();

				int classesCount = ois.readInt();
				for (int j = 0; j < classesCount; j++) {
					record.m_classes.add((String) ois.readObject());
				}
			}
		} catch (EOFException e) {
			s_logger.warn("BUG: We should not get an EOFException. Ignoring for now.");
		} catch (Exception e) {
			s_logger.error(
				"Failed to read schemas from schema registry",
				e);
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e1) {
				}
			}
		}
	}

	static void save(File file) {
		synchronized (s_uriToRecord) {
			FileOutputStream fos = null;
			ObjectOutputStream oos = null;
			try {
				fos = new FileOutputStream(file);
				oos = new ObjectOutputStream(fos);
				
				Set values = new HashSet(s_uriToRecord.values());
				
				oos.writeInt(values.size());
	
				Iterator i = values.iterator();
				while (i.hasNext()) {
					Record record = (Record) i.next();
					
					oos.writeObject(record.m_uri);
					oos.writeBoolean(record.m_explicit);
					oos.writeInt(record.m_classes.size());
					
					Iterator j = record.m_classes.iterator();
					while (j.hasNext()) {
						String classURI = (String) j.next();
						
						oos.writeObject(classURI);
					}
				}
			} catch (Exception e) {
				s_logger.error(
					"Failed to write schemas to schema registry",
					e);
			} finally {
				if (oos != null) {
					try {
						oos.close();
					} catch (IOException e) {
					}
				}
				if (fos != null) {
					try {
						fos.close();
					} catch (IOException e) {
					}
				}
			}
		}
	}
	
	static void load(File file, Profile profile) {
		if (!file.exists()) {
			return;
		}
		
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);

			ObjectInputStream ois = new ObjectInputStream(fis);
			
			int count = ois.readInt();
			
			for (int i = 0; i < count; i++) {
				StructuredSchema sSchema = 
					getStructuredSchema(profile, (String) ois.readObject());
			}
		} catch (EOFException e) {
			s_logger.warn("BUG: We should not get an EOFException. Ignoring for now.");
		} catch (Exception e) {
			s_logger.error(
				"Failed to read profile specific schemas from schema registry",
				e);
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e1) {
				}
			}
		}
	}

	static void save(File file, Profile profile) {
		synchronized (s_uriToProfileToInstance) {
			FileOutputStream fos = null;
			ObjectOutputStream oos = null;
			try {
				fos = new FileOutputStream(file);
				oos = new ObjectOutputStream(fos);
				
				Set instances = new HashSet();
				
				Iterator i = s_uriToProfileToInstance.values().iterator();
				while (i.hasNext()) {
					Map profileToInstance = (Map) i.next();
					StructuredSchema sSchema = 
						(StructuredSchema) profileToInstance.get(profile);
					
					if (sSchema != null) {
						instances.add(sSchema);
					}
				}
				
				oos.writeInt(instances.size());
	
				i = instances.iterator();
				while (i.hasNext()) {
					StructuredSchema sSchema = (StructuredSchema) i.next();
					
					oos.writeObject(sSchema.m_record.m_uri);
				}
			} catch (Exception e) {
				s_logger.error(
					"Failed to write profile specific schemas to schema registry",
					e);
			} finally {
				if (oos != null) {
					try {
						oos.close();
					} catch (IOException e) {
					}
				}
				if (fos != null) {
					try {
						fos.close();
					} catch (IOException e) {
					}
				}
			}
		}
	}
	
	static String uriToSchemaURI(String uri) {
		int i = uri.lastIndexOf('#');
		if (i < 0) {
			i = uri.lastIndexOf('/');
		}
		if (i < 0) {
			i = uri.lastIndexOf(':');
		}
		
		if (i < 0) {
			return "";
		} else {
			return uri.substring(0, i + 1);
		}
	}

	static Set guessStructuredSchemas(Profile profile, String uri) {
		
		synchronized (s_uriToRecord) {
			Set sSchemas = new HashSet();
			if (uri != null) {
				Iterator i = s_uriToRecord.keySet().iterator();
				while (i.hasNext()) {
					String schemaURI = (String) i.next();
					
					if (uri.startsWith(schemaURI)) {
						sSchemas.add(getStructuredSchema(profile, schemaURI));
					}
				}
			}		
			return sSchemas;
		}
	}
	
	static StructuredSchema getStructuredSchema(Profile profile, String uri) {
		synchronized (s_uriToProfileToInstance) {
			Map profileToInstance = (Map) s_uriToProfileToInstance.get(uri);
			if (profileToInstance == null) {
				profileToInstance = new HashMap();
				s_uriToProfileToInstance.put(uri, profileToInstance);
			}
			
			StructuredSchema instance = (StructuredSchema)
				profileToInstance.get(profile);
			if (instance == null) {
				Record record = getRecord(uri);
				
				instance = new StructuredSchema(profile, record);
				
				profileToInstance.put(profile, instance);
				
				if (profile.getModel().contains(
						profile.getModel().createResource(uri),
						RDF.type, 
						(RDFNode) null)) {
					
					record.m_explicit = true;
				}
			}
			
			return instance;
		}
	}

	static Set getStructuredSchemas(Profile profile) {
		synchronized (s_uriToProfileToInstance) {
			Set sSchemas = new HashSet();
			
			Iterator i = s_uriToProfileToInstance.values().iterator();
			while (i.hasNext()) {
				Map profileToInstance = (Map) i.next();
				
				StructuredSchema instance = (StructuredSchema)
					profileToInstance.get(profile);
				if (instance != null) {
					sSchemas.add(instance);
				}
			}
			
			return sSchemas;
		}
	}
	
	static protected Record getRecord(String uri) {
		synchronized (s_uriToRecord) {
			Record record = (Record) s_uriToRecord.get(uri);
			if (record == null) {
				record = new Record();
				record.m_uri = uri;
				
				s_uriToRecord.put(uri, record);
			}
			return record;
		}
	}
	
	synchronized public void onNewClass(StructuredClass sClass) {
		String classURI = sClass.getURI();
		if (classURI.startsWith(m_record.m_uri)) {
			m_record.m_classes.add(classURI);
			m_classes = null; // clear cache
			s_allObjectsCache.remove(this);
		}
	}
	
	synchronized public void onClassChanges(StructuredClass sClass) {
		String classURI = sClass.getURI();
		if (m_record.m_classes.contains(classURI)) {
			s_allObjectsCache.remove(this);
		}
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredSchema#getURI()
	 */
	synchronized public String getURI() {
		return m_record.m_uri;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredSchema#isExplicit()
	 */
	synchronized public boolean isExplicit() {
		return m_record.m_explicit;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredSchema#getClasses()
	 */
	synchronized public Set getClasses() {
		if (m_classes == null) {
			m_classes = new HashSet();
			
			Iterator i = m_record.m_classes.iterator();
			while (i.hasNext()) {
				String classURI = (String) i.next();
				
				m_classes.add(
					StructuredClass.getStructuredClass(
						m_profile, 
						m_profile.getModel().createResource(classURI),
						false));
			}
		}
		return m_classes;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredSchema#getObjects()
	 */
	synchronized public Set getObjects() {
		Set objects = (Set) s_allObjectsCache.get(this); 
		
		if (objects == null) {
			objects = new HashSet();
			
			Iterator i = getClasses().iterator();
			while (i.hasNext()) {
				StructuredClass sClass = (StructuredClass) i.next();
				
				objects.addAll(sClass.getObjects());
			}
			
			s_allObjectsCache.put(this, objects);
		}			
		return objects;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredSchema#countObjects()
	 */
	synchronized public int countObjects() {
		return getObjects().size();
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredSchema#containsObject(com.hp.hpl.jena.rdf.model.Resource)
	 */
	synchronized public boolean containsObject(Resource object) {
		// TODO Auto-generated method stub
		return false;
	}
}
